<html lang="en-US">

<head>
    
<title>先睹为快，Go2 Error 的挣扎之路 - 花落雨忧</title>

<meta property="og:title" content="先睹为快，Go2 Error 的挣扎之路 - 花落雨忧">



    



    
    <meta property="description" content="大家好，我是煎鱼。
自从 Go 语言在国内火热以来，除去泛型，其次最具槽点的就是 Go 对错误的处理方式，一句经典的 if err != nil 暗号就能认出你是一个 Go 语言爱好者。
自然，大家对 Go error 的关注度更是高涨，Go team 也是，因此在 Go 2 Draft Designs 中正式提到了 error handling（错误处理）的相关草案，希望能够在未来正式的解决这个 &amp;hellip;">
    <meta property="og:description" content="大家好，我是煎鱼。
自从 Go 语言在国内火热以来，除去泛型，其次最具槽点的就是 Go 对错误的处理方式，一句经典的 if err != nil 暗号就能认出你是一个 Go 语言爱好者。
自然，大家对 Go error 的关注度更是高涨，Go team 也是，因此在 Go 2 Draft Designs 中正式提到了 error handling（错误处理）的相关草案，希望能够在未来正式的解决这个 &amp;hellip;">
    






<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1">

<link rel="shortcut icon" href="https://www.lican.site/logo/logo.png" type="image/x-icon" />



<link rel="stylesheet" href="/css/style.min.css" />

<link rel="stylesheet" href="/css/reset.min.css" />




<script src="https://www.lican.site/js/highlight.min.js"></script>

<script>
hljs.configure({ ignoreUnescapedHTML: true })
hljs.highlightAll();
</script>


<script src="https://www.lican.site/js/jquery.min.js"></script>




<link href="https://www.lican.site/css/hugo-code.min.css" rel="stylesheet" />



    <style>
        .post-content img {
            max-width: 400px;
        }
    </style>
</head>

<body id="period" class="home blog">
    <a class="skip-content" href="#main">Press "Enter" to skip to content</a>
    <div id="overflow-container" class="overflow-container">
        <header class="site-header" id="site-header" role="banner">
    <div class="max-width">
        <div id="title-container" class="title-container">
            <div id="site-title" class="site-title"><a href="/">花落雨忧</a></div>
            <p class="tagline">思所及 力所行 方为真.</p>
        </div>
        
        <div id="menu-primary-container" class="menu-primary-container">
            <div class="icon-container">
            </div>
            <div id="menu-primary" class="menu-container menu-primary" role="navigation">
                <nav class="menu">
                    <ul id="menu-primary-items" class="menu-primary-items">
                        
                        
                        <li id="menu-item-0"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/posts" aria-current="page" tabindex="0">首页</a></li>
                        
                        <li id="menu-item-1"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/tech/" aria-current="page" tabindex="1">技术文档</a></li>
                        
                        <li id="menu-item-2"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/article/" aria-current="page" tabindex="2">文章</a></li>
                        
                        <li id="menu-item-3"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/project/" aria-current="page" tabindex="3">项目</a></li>
                        
                        <li id="menu-item-4"
                            class="menu-item menu-item-type-custom menu-item-object-custom ">
                            <a href="/about/" aria-current="page" tabindex="4">关于</a></li>
                        
                    </ul>
                </nav>
            </div>
        </div>
    </div>
</header>
        <div id="primary-container" class="primary-container">
            <div class="max-width">
                <section id="main" class="main" role="main">
                    <div id="loop-container" class="loop-container">
                        <div
                            class="post type-post status-publish format-standard hentry entry">
                            <article>
                                <div class="post-container">
                                    <div class="post-header">
                                        <h2 class="post-title">
                                            <a href="/posts/go/go2-errors/">先睹为快，Go2 Error 的挣扎之路</a>
                                        </h2>
                                        
                                        <div class="post-byline">Published on
                                            <a class="date" href="javascript:;">2020/12/03</a>
                                            
                                            
                                            
                                            
                                            
                                        
                                    </div>
                                    <div class="post-content">
                                        <p>大家好，我是煎鱼。</p>
<p>自从 Go 语言在国内火热以来，除去泛型，其次最具槽点的就是 Go 对错误的处理方式，一句经典的 <code>if err != nil</code>  暗号就能认出你是一个 Go 语言爱好者。</p>
<p><img src="https://image.eddycjy.com/381fcb5e85923479666f5be14de3782c.jpeg" alt="image"></p>
<p>自然，大家对 Go error 的关注度更是高涨，Go team 也是，因此在 <a href="https://github.com/golang/proposal/blob/master/design/go2draft.md">Go 2 Draft Designs</a> 中正式提到了 error handling（错误处理）的相关草案，希望能够在未来正式的解决这个问题。</p>
<p>在今天这篇文章中，我们将一同跟踪 Go2 error，看看他是怎么 “挣扎” 的，能不能破局？</p>
<h2 id="为什么要吐槽-go1">为什么要吐槽 Go1</h2>
<p>要吐槽 Go1 error，就得先知道为什么大家到底是在喷 Error 哪里处理的不好。在 Go 语言中，error 其实本质上只是个 Error 的 <code>interface</code>：</p>
<pre><code>type error interface {
    Error() string
}
</code></pre><p>实际的应用场景如下：</p>
<pre><code>func main() {
	x, err := foo()
	if err != nil {
		 // handle error
	}
}
</code></pre><p>单纯的看这个例子似乎没什么问题，但工程大了后呢？显然 <code>if err != nil</code> 的逻辑是会堆积在工程代码中，Go 代码里的 <code>if err != nil</code> 甚至会达到工程代码量的 30% 以上：</p>
<pre><code>func main() {
	x, err := foo()
	if err != nil {
		 // handle error
	}
	y, err := foo()
	if err != nil {
		 // handle error
	}
	z, err := foo()
	if err != nil {
		 // handle error
	}
	s, err := foo()
	if err != nil {
		 // handle error
	}
}
</code></pre><p>暴力的对比一下，就发现四行函数调用，十二行错误，还要苦练且精通 IDE 的快速折叠功能，还是比较麻烦的。</p>
<p>另外既然是错误处理，那肯定不单单是一个 <code>return err</code> 了。在工程实践中，项目代码都是层层嵌套的，如果直接写成：</p>
<pre><code>if err != nil {
	return err
}
</code></pre><p>在实际工程中肯定是不行。你怎么知道具体是哪里抛出来的错误信息，实际出错时只能瞎猜。大家又想出了 PlanB，那就是加各种描述信息：</p>
<pre><code>if err != nil {
	logger.Errorf(&quot;煎鱼报错 err：%v&quot;, err)
	return err
}
</code></pre><p>虽然看上去人模人样的，在实际出错时，也会遇到新的问题，因为你要去查这个错误是从哪里抛出来的，单纯几句错误描述是难以定位的。这时候就会发展成<strong>到处打错误日志</strong>：</p>
<pre><code>func main() {
	err := bar()
	if err != nil {
		logger.Errorf(&quot;bar err：%v&quot;, err)
	}
	...
}

func bar() error {
	_, err := foo()
	if err != nil {
		logger.Errorf(&quot;foo err：%v&quot;, err)
		return err
	}

	return nil
}

func foo() ([]byte, error) {
	s, err := json.Marshal(&quot;hello world.&quot;)
	if err != nil {
		logger.Errorf(&quot;json.Marshal err：%v&quot;, err)
		return nil, err
	}

	return s, nil
}
</code></pre><p>虽然到处打了日志，就会变成错误日志非常多，一旦出问题，人肉可能短时间内识别不出来。且最常见的就是到 IDE 上 <code>ctrl + f</code> 搜索是在哪出错，同时在我们常常会自定义一些错误类型，而在 Go 则需要各种判断和处理：</p>
<pre><code>if err := dec.Decode(&amp;val); err != nil {
    if serr, ok := err.(*json.SyntaxError); ok {
       ...
    }
    return err
}
</code></pre><p>首先你得判断不等于 <code>nil</code>，还得对自定义的错误类型进行断言，整体来讲比较繁琐。</p>
<p>汇总来讲，Go1 错误处理的问题至少有：</p>
<ul>
<li>
<p>在工程实践中，<code>if err != nil</code> 写的烦，代码中一大堆错误处理的判断，占了相当的比例，不够优雅。</p>
</li>
<li>
<p>在排查问题时，Go 的 <code>err</code> 并没有其他堆栈信息，只能自己增加描述信息，层层叠加，打一大堆日志，排查很麻烦。</p>
</li>
<li>
<p>在验证和测试错误时，要自定义错误（各种判断和断言）或者被迫用字符串校验。</p>
</li>
</ul>
<h2 id="go113-的挽尊">Go1.13 的挽尊</h2>
<p>在 2019 年 09 月，Go1.13 正式发布。其中两个比较大的两个关注点分别是包依赖管理 Go modules 的转正，以及错误处理 errors 标准库的改进：</p>
<p><img src="https://image.eddycjy.com/51485fae58cbf9bd92aa19686caf5a27.jpg" alt="image"></p>
<p>在本次改进中，errors 标准库引入了 Wrapping Error 的概念，并增加了 Is/As/Unwarp 三个方法，用于对所返回的错误进行二次处理和识别。同时也是将 Go2 error 预规划中没有破坏 Go1 兼容性的相关功能提前实现了。</p>
<p>简单来讲，Go1.13 后 Go 的 error 就可以嵌套了，并提供了三个配套的方法。例子：</p>
<pre><code>func main() {
	e := errors.New(&quot;脑子进煎鱼了&quot;)
	w := fmt.Errorf(&quot;快抓住：%w&quot;, e)
	fmt.Println(w)
	fmt.Println(errors.Unwrap(w))
}
</code></pre><p>输出结果：</p>
<pre><code>$ go run main.go
快抓住：脑子进煎鱼了
脑子进煎鱼了
</code></pre><p>在上述代码中，变量 <code>w</code> 就是一个嵌套一层的 error。最外层是 “快抓住：”，此处调用 <code>%w</code> 意味着 Wrapping Error 的嵌套生成。因此最终输出了 “快抓住：脑子进煎鱼了”。</p>
<p>需要注意的是，Go 并没有提供 <code>Warp</code> 方法，而是直接扩展了 <code>fmt.Errorf</code> 方法。而下方的输出由于直接调用了 <code>errors.Unwarp</code> 方法，因此将 “取” 出一层嵌套，最终直接输出 “脑子进煎鱼了”。</p>
<p>对 Wrapping Error 有了基本理解后，我们简单介绍一下三个配套方法：</p>
<pre><code>func Is(err, target error) bool
func As(err error, target interface{}) bool
func Unwrap(err error) error
</code></pre><h3 id="errorsis">errors.Is</h3>
<p>方法签名：</p>
<pre><code>func Is(err, target error) bool
</code></pre><p>方法例子：</p>
<pre><code>func main() {
	if _, err := os.Open(&quot;non-existing&quot;); err != nil {
		if errors.Is(err, os.ErrNotExist) {
			fmt.Println(&quot;file does not exist&quot;)
		} else {
			fmt.Println(err)
		}
	}

}
</code></pre><p><code>errors.Is</code> 方法的作用是判断所传入的 err 和 target 是否同一类型，如果是则返回 true。</p>
<h3 id="errorsas">errors.As</h3>
<p>方法签名：</p>
<pre><code>func As(err error, target interface{}) bool
</code></pre><p>方法例子：</p>
<pre><code>func main() {
	if _, err := os.Open(&quot;non-existing&quot;); err != nil {
		var pathError *os.PathError
		if errors.As(err, &amp;pathError) {
			fmt.Println(&quot;Failed at path:&quot;, pathError.Path)
		} else {
			fmt.Println(err)
		}
	}

}
</code></pre><p><code>errors.As</code> 方法的作用是从 err 错误链中识别和 target 相同的类型，如果可以赋值，则返回 true。</p>
<h3 id="errorsunwarp">errors.Unwarp</h3>
<p>方法签名：</p>
<pre><code>func Unwrap(err error) error
</code></pre><p>方法例子：</p>
<pre><code>func main() {
	e := errors.New(&quot;脑子进煎鱼了&quot;)
	w := fmt.Errorf(&quot;快抓住：%w&quot;, e)
	fmt.Println(w)
	fmt.Println(errors.Unwrap(w))
}
</code></pre><p>该方法的作用是将嵌套的 error 解析出来，若存在多级嵌套则需要调用多次 Unwarp 方法。</p>
<h2 id="民间自救-pkgerrors">民间自救 pkg/errors</h2>
<p>Go1 的 error 处理固然存在许多问题，因此在 Go1.13 前，早已有 “民间” 发现没有上下文调试信息在实际工程应用中存在严重的体感问题。因此 <code>github.com/pkg/errors</code> 在 2016 年诞生了，目前该库也已经受到了极大的关注。</p>
<p>官方例子如下：</p>
<pre><code>type stackTracer interface {
    StackTrace() errors.StackTrace
}

err, ok := errors.Cause(fn()).(stackTracer)
if !ok {
    panic(&quot;oops, err does not implement stackTracer&quot;)
}

st := err.StackTrace()
fmt.Printf(&quot;%+v&quot;, st[0:2]) // top two frames

// Example output:
// github.com/pkg/errors_test.fn
//	/home/dfc/src/github.com/pkg/errors/example_test.go:47
// github.com/pkg/errors_test.Example_stackTrace
//	/home/dfc/src/github.com/pkg/errors/example_test.go:127
</code></pre><p>简单来讲，就是对 Go1 error 的上下文处理进行了优化和处理，例如类型断言、调用堆栈等。若有兴趣的小伙伴可以自行到 <code>github.com/pkg/errors</code> 进行学习。</p>
<p>另外你可能会发现 Go1.13 新增的 Wrapping Error 体系与 <code>pkg/errors</code> 有些相像。你并没有体会错，Go team 接纳了相关的意见，对 Go1 进行了调整，但调用堆栈这块因综合原因暂时没有纳入。</p>
<h2 id="go2-error-要解决什么问题">Go2 error 要解决什么问题</h2>
<p>在前面我们聊了 Go1 error 的许多问题，以及 Go1.13 和 <code>pkg/errors</code> 的自救和融合。你可能会疑惑，那&hellip;Go2 error 还有出场的机会吗？即使 Go1 做了这些事情，Go1 error 还有问题吗？</p>
<p>并没有解决，<code>if err != nil</code> 依旧一把梭，目前社区声音依然认为 Go 语言的错误处理要改进。</p>
<h2 id="go2-error-proposal">Go2 error proposal</h2>
<p>在 2018 年 8 月，官方正式公布了 <a href="https://go.googlesource.com/proposal/+/master/design/go2draft.md">Go 2 Draft Designs</a>，其中包含泛型和错误处理机制改进的初步草案：</p>
<p><img src="https://image.eddycjy.com/48b07b14442b1832c09eb6e2bc35fb6b.jpg" alt="image"></p>
<p>注：Go1.13 正式将一些不破坏 Go1 兼容性的 Error 特性加入到了 main branch，也就是前面提到的 Wrapping Error。</p>
<h3 id="错误处理error-handling">错误处理（Error Handling）</h3>
<p>第一个要解决的问题就是大量 <code>if err != nil</code> 的问题，针对此提出了 <a href="https://github.com/golang/proposal/blob/master/design/go2draft-error-handling-overview.md">Go2 error handling</a> 的草案设计。</p>
<p>简单例子：</p>
<pre><code>if err != nil {
	return err
}
</code></pre><p>优化后的方案如下：</p>
<pre><code>func CopyFile(src, dst string) error {
	handle err {
		return fmt.Errorf(&quot;copy %s %s: %v&quot;, src, dst, err)
	}

	r := check os.Open(src)
	defer r.Close()

	w := check os.Create(dst)
	handle err {
		w.Close()
		os.Remove(dst) // (only if a check fails)
	}

	check io.Copy(w, r)
	check w.Close()
	return nil
}
</code></pre><p>主函数：</p>
<pre><code>func main() {
	handle err {
		log.Fatal(err)
	}

	hex := check ioutil.ReadAll(os.Stdin)
	data := check parseHexdump(string(hex))
	os.Stdout.Write(data)
}
</code></pre><p>该提案引入了两种新的语法形式，首先是 <code>check</code> 关键字，其可以选中一个表达式 <code>check f(x, y, z)</code> 或 <code>check err</code>，其将会标识这是一个显式的错误检查。</p>
<p>其次引入了 <code>handle</code> 关键字，用于定义错误处理程序流转，逐级上抛，依此类推，直到处理程序执行 <code>return</code> 语句，才正式结束。</p>
<h3 id="错误值打印error-printing">错误值打印（Error Printing）</h3>
<p>第二个要解决的问题是错误值（Error Values）、错误检查（Error Inspection）的问题，其引申出错误值打印（Error Printing）的问题，也可以认为是错误格式化的不便利。</p>
<p>官方针对此提出了提出了 <a href="https://github.com/golang/proposal/blob/master/design/go2draft-error-values-overview.md">Error Values</a> 和 <a href="https://github.com/golang/proposal/blob/master/design/go2draft-error-printing.md">Error Printing</a> 的草案设计。</p>
<p>简单例子如下：</p>
<pre><code>if err != nil {
	return fmt.Errorf(&quot;write users database: %v&quot;, err)
}
</code></pre><p>优化后的方案如下：</p>
<pre><code>package errors

type Wrapper interface {
	Unwrap() error
}

func Is(err, target error) bool
func As(type E)(err error) (e E, ok bool)
</code></pre><p>该提案增加了错误链的 Wrapping Error 概念，并同时增加 <code>errors.Is</code> 和 <code>errors.As</code> 的方法，与前面说到的 Go1.13 的改进一致，不再赘述。</p>
<p>需要留意的是，Go1.13 并没有实现 <code>%+v</code> 输出调用堆栈的需求，因为此举会破坏 Go1 兼容性和产生一些性能问题，大概会在 Go2 加入。</p>
<h2 id="try-catch-不香吗">try-catch 不香吗</h2>
<p>社区中另外一股声音就是直指 Go 语言反人类不用 <code>try-catch</code> 的机制，在社区内也产生了大量的探讨，具体可以看看相关的提案 <a href="https://github.com/golang/go/issues/32437">Proposal: A built-in Go error check function, &ldquo;try&rdquo;</a>。</p>
<p>目前该提案已被拒绝，具体可参见 <a href="https://github.com/golang/go/issues/32437#issuecomment-512035919">go/issues/32437#issuecomment-512035919</a> 和 <a href="https://golang.org/doc/faq#exceptions">Why does Go not have exceptions</a>。</p>
<h2 id="总结">总结</h2>
<p>在这篇文章中，我们介绍了目前 Go1 Error 的现状，概括了大家对 Go 语言错误处理的常见问题和意见。同时还介绍了在这几年间，Go team 针对 Go2、Go1.13 Error 的持续优化和探索。</p>
<p>如果是你，你会怎么去优化目前 Go 语言的错误处理机制呢，现在 Go2 error proposal 你又是否认可？</p>
<h2 id="参考">参考</h2>
<ul>
<li>
<p><a href="https://qcrao.com/2019/09/18/golang-error-break-through">Golang error 的突围</a></p>
</li>
<li>
<p><a href="https://www.zhihu.com/question/330263279">为什么 Go 语言的 Error Handling 是一个败笔</a></p>
</li>
<li>
<p><a href="https://www.flysnow.org/2019/09/06/go1.13-error-wrapping.html">Go语言(golang)新发布的1.13中的Error Wrapping深度分析</a></p>
</li>
</ul>


                                        
                                        
                                        
                                        <div class="rp4wp-related-posts">
                                            <h3>相关文章</h3>
                                            <ul>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/gophercon2020-errors/">重磅：Go errors 将不会有任何进一步的改进计划</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/go16-preview/">为什么 Go 的泛型一拖再拖？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/go11/">Go 语言今年 11 岁，何去何从，现状到底如何？</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/reflect/">解密 Go 语言之反射 reflect</a>
                                                    </div>
                                                </li>
                                                
                                                <li>
                                                    <div class="rp4wp-related-post-content">
                                                        <a
                                                            href="https://www.lican.site/posts/go/go-bootstrap/">Go 应用程序是怎么运行起来的？</a>
                                                    </div>
                                                </li>
                                                
                                            </ul>
                                        </div>
                                        
                                        
                                    </div>

                                    
                                    
                                    

                                    
                                    <div class="post-meta">
                                        
                                        
                                        <div class="post-tags">
                                            <ul>
                                            
                                            <li>
                                                <a href="/tags/go" title="View all posts tagged match">go</a>
                                            </li>
                                            
                                            </ul>
                                        </div>
                                        
                                        
                                        <nav class="further-reading">
                                            
                                            <div class="previous">
                                                <span>&lt;&lt; Prev</span>
                                                <a href="https://www.lican.site/posts/go/gophercon2020-errors/"
                                                    rel="prev">重磅：Go errors 将不会有任何进一步的改进计划</a> </div>
                                            
                                            
                                            <div class="next">
                                                <span>Next >></span>
                                                <a href="https://www.lican.site/posts/go/go-concurrent-lock/">Go 并发：一些有趣的现象和要避开的 “坑”</a> 
                                            </div>
                                            
                                        </nav>
                                    </div>
                                    
                                    

                                    
                                    
                                    

                                </div>
                            </article>
                        </div>
                    </div>
                </section>
                <aside class="sidebar sidebar-primary" id="sidebar-primary" role="complementary">
    <h1 class="screen-reader-text">Sidebar</h1>
    

    
    
    <section id="text-2" class="widget widget_text">
        <div class="textwidget">
            
            <div id="profile">
                <div id="profile_picture"><img src="https://www.lican.site/logo/logo.png"></div>
                <div id="profile_intro">
                    <p><span class="name">Lican</span></p>
                    <p class="intro">全栈开发者，爱好造轮子。</p>
                </div>
            </div>
            
            <p>
                <script type="text/javascript">
                    (function ($) {
                        $(document).ready(function () {
                            var menuPrimaryContainer = $('#menu-primary-container');
                            var profile = $('#text-2');
                            $('#toggle-navigation').click(function () {
                                if (menuPrimaryContainer.hasClass('open')) {
                                    profile.removeClass('open');
                                } else {
                                    profile.addClass('open');
                                }
                            });
                        });
                    })(jQuery);
                </script>
            </p>
        </div>
    </section>
    
    
    
    
    
    <section id="text-5" class="widget widget_text">
        <h2 class="widget-title">开源项目</h2>
        <div class="textwidget">
            <div id="projects" style="line-height: 22px;">
                
                <a href="https://github.com/idoubi/gonews"
                    target="_blank">gonews</a>: &nbsp;Daily news for golang<br>
                
                <a href="https://github.com/idoubi/sql2struct"
                    target="_blank">sql2struct</a>: &nbsp;Generate go struct according to SQL<br>
                
                <a href="https://github.com/idoubi/goz"
                    target="_blank">goz</a>: &nbsp;Request library used in golang<br>
                
        </div>
    </section>
    
    

    
    
    
    
    

    
    
    

    
    
    

    
    
    
    
</aside>
            </div>
        </div>

        <footer id="site-footer" class="site-footer" role="contentinfo">
    <div class="max-width">
    </div>
    <div class="footer">
        <div id="footercontent">
            © lican.vip All rights reserved<br/>
            Built with Hugo Theme <a href="https://github.com/idoubi/hugo-theme-period" target="_blank">Period</a>
        </div>
    </div>
</footer>

<script>
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?e8351b6d4626d5881d439ea1f6184baa";
      var s = document.getElementsByTagName("script")[0]; 
      s.parentNode.insertBefore(hm, s);
    })();
</script>
    
    
    </div>
    
</body>

</html>